﻿using System.Numerics;

// ReSharper disable MemberCanBePrivate.Global
// ReSharper disable UnusedAutoPropertyAccessor.Global
// ReSharper disable InconsistentNaming

namespace Konata.Core.Utils.Ecdh;

internal readonly struct EllipticCurve
{
    // @formatter:off
    
    /// <summary>
    /// ref https://neuromancer.sk/std/secg/secp192k1
    /// </summary>
    public static readonly EllipticCurve SecP192k1 = new (
        new BigInteger(new byte[] {
            0x37, 0xEE, 0xFF, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF,
            0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
            0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0 }),
        0,
        3,
        new BigInteger(new byte[] {
            0x7D, 0x6C, 0xE0, 0xEA, 0xB1, 0xD1, 0xA5, 0x1D,
            0x34, 0xF4, 0xB7, 0x80, 0x02, 0x7D, 0xB0, 0x26,
            0xAE, 0xE9, 0x57, 0xC0, 0x0E, 0xF1, 0x4F, 0xDB, 0 }),
        new BigInteger(new byte[] {
            0x9D, 0x2F, 0x5E, 0xD9, 0x88, 0xAA, 0x82, 0x40,
            0x34, 0x86, 0xBE, 0x15, 0xD0, 0x63, 0x41, 0x84,
            0xA7, 0x28, 0x56, 0x9C, 0x6D, 0x2F, 0x2F, 0x9B, 0 }),
        new BigInteger(new byte[] {
            0x8D, 0xFD, 0xDE, 0x74, 0x6A, 0x46, 0x69, 0x0F,
            0x17, 0xFC, 0xF2, 0x26, 0xFE, 0xFF, 0xFF, 0xFF,
            0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0 }),
        1,
        24, 
        24 // Pack size
    );

    /// <summary>
    /// ref https://neuromancer.sk/std/x962/prime256v1
    /// </summary>
    public static readonly EllipticCurve Prime256v1 = new (
        new BigInteger(new byte[] {
            0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
            0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x01, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0}),
        new BigInteger(new byte[] {
            0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
            0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
            0x01, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0}),
        new BigInteger(new byte[] {
            0x4B, 0x60, 0xD2, 0x27, 0x3E, 0x3C, 0xCE, 0x3B, 
            0xF6, 0xB0, 0x53, 0xCC, 0xB0, 0x06, 0x1D, 0x65, 
            0xBC, 0x86, 0x98, 0x76, 0x55, 0xBD, 0xEB, 0xB3, 
            0xE7, 0x93, 0x3A, 0xAA, 0xD8, 0x35, 0xC6, 0x5A, 0}),
        new BigInteger(new byte[] {
            0x96, 0xC2, 0x98, 0xD8, 0x45, 0x39, 0xA1, 0xF4, 
            0xA0, 0x33, 0xEB, 0X2D, 0x81, 0x7D, 0x03, 0x77, 
            0xF2, 0x40, 0xA4, 0x63, 0xE5, 0xE6, 0xBC, 0xF8, 
            0x47, 0x42, 0x2C, 0xE1, 0xF2, 0xD1, 0x17, 0x6B, 0}),
        new BigInteger(new byte[] {
            0xF5, 0x51, 0xBF, 0x37, 0x68, 0x40, 0xB6, 0xCB, 
            0xCE, 0x5E, 0x31, 0x6B, 0x57, 0x33, 0xCE, 0x2B, 
            0x16, 0x9E, 0x0F, 0x7C, 0x4A, 0xEB, 0xE7, 0x8E, 
            0x9B, 0x7F, 0x1A, 0xFE, 0xE2, 0x42, 0xE3, 0x4F, 0}),
        new BigInteger(new byte[] {
            0x51, 0x25, 0x63, 0xFC, 0xC2, 0xCA, 0xB9, 0xF3, 
            0x84, 0x9E, 0x17, 0xA7, 0xAD, 0xFA, 0xE6, 0xBC, 
            0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
            0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0}),
        1,
        32,
        16 // Pack size
    );
    
// @formatter:on

    public BigInteger P { get; }

    public BigInteger A { get; }

    public BigInteger B { get; }

    public EllipticPoint G { get; }

    public BigInteger N { get; }

    public BigInteger H { get; }

    public int Size { get; }

    public int PackSize { get; }

    /// <summary>
    /// All points on an elliptic curve verify,
    /// by definition, the curve equation, usually written as
    /// Y2 = X3 + aX + b
    /// </summary>
    /// <param name="point"></param>
    /// <returns></returns>
    public bool CheckOn(EllipticPoint point)
        => (point.Y * point.Y - point.X * point.X * point.X - A * point.X - B) % P == 0;

    private EllipticCurve(BigInteger p, BigInteger a, BigInteger b,
        BigInteger gx, BigInteger gy, BigInteger n, BigInteger h, int size, int packSize)
    {
        P = p;
        A = a;
        B = b;
        G = new EllipticPoint(gx, gy);
        N = n;
        H = h;
        Size = size;
        PackSize = packSize;
    }
}
